//
//  MainViewController.m
//  RunSketch
//
//  Created by Brian on 10/1/09.
//  Copyright Tellart 2009. All rights reserved.
//

#import "UserPreferencesController.h"
#import "MainViewController.h"
#import "MainView.h"
#import "SketchUpdateController.h"
#import "AutoUpdateHTTPConnection.h"
#import "localhostAddresses.h"

#define kCarrierToneFileName @"carrier"
#define kCarrierToneFileType @"wav"

#define kNadaMobileIOHelper_useLocation YES
#define kNadaMobileIOHelper_useAccelerometer YES
#define kNadaMobileIOHelper_useAudio YES
#define kNadaMobileIOHelper_useProximity YES
#define kNadaMobileIOHelper_useOrientation YES
#define kNadaMobileIOHelper_useBattery YES
#define kNadaMobileIOHelper_useIPodRemote YES

#define kIOPackPollPeriod             0.080f
#define kIOPackPollPeriodBoosted      0.030f
#define kLoopsPerPollForReverseCall     2
#define kSketchReverseCallPollFcn       @"NadaCore.__skc_poll"
#define kSketchReverseCallSeparator     @"|"
#define kSketchCallbackSetup            @"sketchSetup"
#define kSketchCallbackOnIOPack         @"NadaCore.__sketchOnIOPackInput"
#define kSketchCallbackOnAccelerate     @"sketchOnAccelerate"
#define kSketchCallbackOnLocation       @"sketchOnLocation"
#define kSketchCallbackOnAudioAmplitude @"sketchOnAudioAmplitude"
#define kSketchCallbackOnAudioProfile   @"sketchOnAudioProfile"
#define kSketchCallbackOnHeading        @"sketchOnHeading"
#define kSketchCallbackOnProximity      @"sketchOnProximity"
#define kSketchCallbackOnOrientation    @"sketchOnOrientation"
#define kSketchCallbackOnBattery        @"sketchOnBattery"
#define kSketchCallbackOnSimpleSocketRecv @"sketchOnSocketReceive"
#define kSketchCallbackOnCameraReadComplete @"sketchOnCameraRead"
#define kSketchCallbackOnSoundRecordComplete @"sketchOnSoundRecord"
#define kSketchCallbackOnIPodPlay       @"sketchOnRemoteControlPlay"
#define kSketchCallbackOnIPodPause      @"sketchOnRemoteControlPause"
#define kSketchCallbackOnIPodVolume     @"sketchOnRemoteControlVolume"
#define kFlowControlWaitForSetTimeout   1.0f
#define kSketchLoadPhase2CountdownStart   (2.0 / kIOPackPollPeriod)

#define REVERSE_FCN_setIOPack           @"100"
#define REVERSE_FCN_soundPlay           @"101"
#define REVERSE_FCN_soundRecord         @"102"
#define REVERSE_FCN_setSensorTone       @"103"
#define REVERSE_FCN_setProximitySensor  @"104"
#define REVERSE_FCN_cameraRead          @"105"
#define REVERSE_FCN_tweet               @"106"
#define REVERSE_FCN_socketAccept        @"107"
#define REVERSE_FCN_socketSend          @"108"
#define REVERSE_FCN_peerListen          @"109"
#define REVERSE_FCN_peerSend            @"110"
#define REVERSE_FCN_videoPlay           @"111"
#define REVERSE_FCN_showCameraPreviewInBackground   @"112"
#define REVERSE_FCN_hideCameraPreview   @"113"

#define kPlaySoundVibrate               @"#vibrate"


@implementation MainViewController

@synthesize cameraController, lastCameraImage, lastCameraImageURL, sketchWebView, mpVolumeViewParentView, sketchLoadingActivityIndicator /*, dangerousFlipside*/;


- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {

        warnedAboutMissingCamera = NO;

        userPrefs = [[UserPreferencesController alloc] init];

        //set up the NADA Mobile IO helper
        ioHelper = [[[NADAMobileIOHelper alloc] init] retain];
        
        //init and allocate the remote IOPack
        remoteIOPackTransport = [[[TCPSocketIOPackTransport alloc] init] retain];
        
        //init and allocate the local IOPack
        localIOPackTransport = [[[LocalSerialIOPackTransport alloc] init] retain];
        
        //allows a site to view the camera on iPhones by making a transparent "hole" in the page
        self.view.backgroundColor = [UIColor blackColor];
        sketchWebView.backgroundColor = [UIColor clearColor];
        [sketchWebView setOpaque:NO];
        
        [self initAutoUpdateServer];
    }
    return self;
}

- (void)dealloc {
    //[self stopAutoUpdateServer];
    [self deallocAutoUpdateServer];
	[self disableSensors];
    [ioHelper release];
    [remoteIOPackTransport release];
    [localIOPackTransport release];
    
    userPrefs.lastShutdownStatus = @"OK";
    [userPrefs store];

    [cameraController release];
    [lastCameraImage release];
    [lastCameraImageURL release];

    //[dangerousFlipside release];
    
    if (self.sketchLoadingActivityIndicator) [sketchLoadingActivityIndicator release];
    if (self.sketchWebView) [sketchWebView release];

    [super dealloc];
}


 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
 - (void)viewDidLoad {
    [super viewDidLoad];
    [self initializeUpdateLoop];
    [self loadSketch];
    
    //prevent the system volume alerts from being displayed by using a hidden MPVolumeView
    mpVolumeViewParentView.backgroundColor = [UIColor clearColor];
    //mpVolumeViewParentView.alpha = 0.0;
    mpVolumeViewParentView.hidden = YES;
    mpVolumeViewParentView.userInteractionEnabled = NO;
    MPVolumeView *systemVolumeSlider = [[MPVolumeView alloc] initWithFrame: mpVolumeViewParentView.bounds];
    [mpVolumeViewParentView addSubview: systemVolumeSlider];
    [systemVolumeSlider release];
}
 
/* 
 * set up the HTTP server used for auto-updating
 *
 */
 - (void) initAutoUpdateServer
{
    NSString *root = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) objectAtIndex:0];
    
    httpServer = [HTTPServer new];
    [httpServer setType:@"_http._tcp."];
    [httpServer setConnectionClass:[AutoUpdateHTTPConnection class]];
    [httpServer setDocumentRoot:[NSURL fileURLWithPath:root]];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(autoUpdateReceivedRequest:) name:kNotification_AutoUpdateHTTPConnectionReceivedAutoUpdateRequest object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localhostAddressesResolved:) name:@"LocalhostAdressesResolved" object:nil];
    [localhostAddresses performSelectorInBackground:@selector(list) withObject:nil];

    [self startAutoUpdateServer];
}

- (void) deallocAutoUpdateServer
{
	[httpServer release];
}

- (void) autoUpdateReceivedRequest:(NSNotification *) notification
{
    NSString *hostName = [NSString stringWithFormat:@"%@", [[notification object] copy]];
    NSLog(@"[MainViewController autoUpdateReceivedRequest]: received auto-update request from %@", hostName);
    
    if (autoUpdateRequested) {
        //already in the process of performing an auto-update
        NSLog(@"[MainViewController autoUpdateReceivedRequest]: will ignore auto-udpate request because we're already in the process of an auto-update");
        return;
    }
    
    [self setRemoteIOPackHostName:hostName];
    autoUpdateRequested = YES;
    [self showInfo];
}

- (void) localhostAddressesResolved:(NSNotification *) notification
{
	NSLog(@"[MainViewController localhostAddressesResolved]:");

	if(notification)
	{
		[addresses release];
		addresses = [[notification object] copy];
		NSLog(@"[MainViewController localhostAddressesResolved]: addresses: %@", addresses);
	}

	if(addresses == nil)
	{
		return;
	}
	
	NSString *info;
	UInt16 port = [httpServer port];
	
	NSString *localIP = nil;
	
	localIP = [addresses objectForKey:@"en0"];
	
	if (!localIP)
	{
		localIP = [addresses objectForKey:@"en1"];
	}

	if (!localIP)
		info = @"Wifi: No Connection!\n";
	else
		info = [NSString stringWithFormat:@"http://iphone.local:%d		http://%@:%d\n", port, localIP, port];

	NSString *wwwIP = [addresses objectForKey:@"www"];

	if (wwwIP)
		info = [info stringByAppendingFormat:@"Web: %@:%d\n", wwwIP, port];
	else
		info = [info stringByAppendingString:@"Web: Unable to determine external IP\n"];

    NSLog(@"[MainViewController localhostAddressesResolved]: %@", info);
}


- (void) startAutoUpdateServer
{
    NSError *error;
    if(![httpServer start:&error])
    {
        NSLog(@"[MainViewController startAutoUpdateServer]: Error starting HTTP Server: %@", error);
    }
}

- (void) stopAutoUpdateServer
{
    [httpServer stop];
}


/*
 // Override to allow orientations other than the default portrait orientation.
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 // Return YES for supported orientations
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
 }
 */


- (NSString *) flipsideViewControllerGetRemoteIOPackHostName
{
    return userPrefs.remoteIOPackHostName;
}

- (BOOL) flipsideViewControllerWasAutoUpdateRequested;
{
    return autoUpdateRequested;
}

- (BOOL) flipsideViewControllerIsRemoteIOPackConnected
{
    return userPrefs.enableRemoteIOPack && remoteIOPackTransport.isConnectedAndReady;
}

- (BOOL) flipsideViewControllerIsRemoteIOPackError
{
    return userPrefs.enableRemoteIOPack && !remoteIOPackTransport.isConnectedAndReady;
}

- (BOOL) flipsideViewControllerGetEnableLocalIOPack
{
    return userPrefs.enableLocalIOPack;
}

- (BOOL) flipsideViewControllerIsLocalIOPackConnected
{
    return userPrefs.enableLocalIOPack && localIOPackTransport.isConnectedAndReady;
}

- (BOOL) flipsideViewControllerIsLocalIOPackError
{
    return userPrefs.enableLocalIOPack && !localIOPackTransport.isConnectedAndReady;
}

- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller reloadSketch:(BOOL)reload
{
    autoUpdateRequested = NO;
    
    ioHelper.myDelegate = self;
    //if (reload) [self loadSketch];
    [userPrefs store];
	[self dismissModalViewControllerAnimated:NO];

    if (reload) [self loadSketch];
    
    /*
    if (reload) {
        //load the sketch in 4 seconds
        [NSTimer
            scheduledTimerWithTimeInterval:(NSTimeInterval)4.0
            target:self
            selector:@selector(loadSketch)
            userInfo:nil
            repeats:NO];
    }
    */
    
    /*
    if (reload) [self performSelectorOnMainThread:@selector(loadSketch) 
     withObject:nil waitUntilDone:YES];
    */
    /*
    if (reload) {
        if (self.sketchWebView) {
            [self.sketchWebView removeFromSuperview];
            [sketchWebView release];
            self.sketchWebView = nil;
        }
    }
    */
    
}

- (void)flipsideViewControllerDidChangeRemoteIOPackHostName:(FlipsideViewController *)controller to:(NSString*)hostName
{
    NSLog(@"[MainViewController flipsideViewControllerDidChangeRemoteIOPackHostName:controller to:%@]", hostName);
    [self setRemoteIOPackHostName:hostName];
}

- (void) setRemoteIOPackHostName:(NSString *)hostName
{
    userPrefs.remoteIOPackHostName = hostName;
    userPrefs.enableRemoteIOPack = (userPrefs.remoteIOPackHostName != nil) && (userPrefs.remoteIOPackHostName != NULL) && ([userPrefs.remoteIOPackHostName length] > 0);
    [self disableIOPacks];
    [self enableIOPacks];
}

- (void)flipsideViewControllerDidChangeFlowControl:(FlipsideViewController *)controller to:(BOOL)state
{
    //[remoteIOPackTransport.ioPack setFlowControl:state];
}

- (void)flipsideViewControllerDidChangeIOPackBoost:(FlipsideViewController *)controller to:(BOOL)state
{
    userPrefs.useSensorBoost = state;
}

- (void)flipsideViewControllerDidChangeLocalIOPackEnabled:(FlipsideViewController *)controller to:(BOOL)state
{
    userPrefs.enableLocalIOPack = state;
    [self disableIOPacks];
    [self enableIOPacks];
}


- (IBAction) reloadSketch
{
    NSLog(@"[MainViewController reloadSketch]");
    [sketchWebView reload];
}


- (IBAction) loadSketch
{
    NSLog(@"[MainViewController loadSketch]");
    
    sensorToneState = NO;
    sensorProximityState = NO;
    
    self.lastCameraImage = nil;
    self.lastCameraImageURL = nil;

    if (sketchLoaded) {
        [sketchWebView reload];
    } else {
        NSURL *aURL = [SketchUpdateController getSketchEntryPointURL];
        NSLog(@"MainViewController: loading sketch from entry point %@", aURL);
        NSURLRequest *aRequest = [NSURLRequest requestWithURL:aURL
            cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
            timeoutInterval:(NSTimeInterval)90];
        //load the index.html file into the web view.
        [sketchWebView loadRequest:aRequest];
    }
}


- (IBAction)showInfo {    
	
	FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:@"FlipsideView" bundle:nil];
	controller.delegate = self;
    ioHelper.myDelegate = controller;
    
	controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
    //self.dangerousFlipside = controller;
	[self presentModalViewController:controller animated:YES];
	
	[controller release];
    
    //[dangerousFlipside release];
    //self.dangerousFlipside = nil;
}



/*
 // Override to allow orientations other than the default portrait orientation.
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 // Return YES for supported orientations
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
 }
 */

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
    
    self.sketchWebView.delegate = nil;
    self.sketchWebView = nil;
}


/*******************************************************************************************************************/

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
    NSLog(@"[MainViewController webView didFailLoadWithError]");
    NSLog(@"-------------------------");
    NSLog([error localizedDescription]);
    NSLog([error localizedFailureReason]);
    NSLog(@"%@", [error userInfo]);
    NSLog(@"-------------------------");
    if ([error code] == NSURLErrorFileIsDirectory) {
        //tried to load a directory, so we'll apprend "index.html" to the end of it and try loading that instead
        //the offending URL is in the userInfo dictionary of the NSError, under the key called NSErrorFailingURLStringKey
        NSString *badUrlString = [[error userInfo] objectForKey:NSErrorFailingURLStringKey];
        //entry point is a directory!
        NSURL *aURL = [NSURL URLWithString: [NSString stringWithFormat:@"%@/index.html", badUrlString]];
        NSLog(@"MainViewController webView didFailLoadWithError: loading sketch from new entry point %@", aURL);
        NSURLRequest *aRequest = [NSURLRequest requestWithURL:aURL
            cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
            timeoutInterval:(NSTimeInterval)90];
        //load the index.html file into the web view.
        [sketchWebView loadRequest:aRequest];
    }
}
- (void)webViewDidStartLoad:(UIWebView *)webView{
	NSLog(@"webView: loading started");
    /*
     * for debugging ONLY - this will create a memory leak
     */
    /*
    NSURL *aURL = [webView.request URL];
    NSString *contents = [NSString stringWithContentsOfURL:aURL];
    NSLog(@"-- source of %@", aURL);
    NSLog(@"%@", contents);
    NSLog(@"");
    NSLog(@"-----------------------------------------------------------");
    */
    /* end debugging
     */
    [self disableSensors];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
    if (!sketchIsReloading) {
        NSLog(@"webView: finished load phase 1 of 2 %@", webView);
        sketchIsReloading = YES;
        sketchWebView.hidden = YES;
        sketchLoadingActivityIndicator.hidden = NO;
        sketchLoadPhase2Countdown = kSketchLoadPhase2CountdownStart;
        //setch phase 2 will finish in the main updateLoop
        
        /*
        //load the sketch in kSketchLoadPhase2Delay seconds
		[[NSRunLoop mainRunLoop] addTimer: [NSTimer
            timerWithTimeInterval:kSketchLoadPhase2Delay
            target:sketchWebView
            selector:@selector(reload)
            userInfo:nil
            repeats:NO] forMode: NSDefaultRunLoopMode];
        */

    } else {
        NSLog(@"webView: finished load phase 2 of 2 %@", webView);
        sketchIsReloading = NO;
        //[self performSelectorOnMainThread:@selector(setupSketchAfterLoad) withObject:nil waitUntilDone:YES];
        [self setupSketchAfterLoad];
        sketchWebView.hidden = NO;
        sketchLoadingActivityIndicator.hidden = YES;
        
    }
}

- (void)setupSketchAfterLoad
{
    NSLog(@"Adding sketching library:");
    NSArray *urls = [SketchUpdateController getSketchingJavaScriptLibraryURLs];
    
    NSEnumerator *e = [urls objectEnumerator];
    id url;

    //add each URL in turn
    while ( (url = [e nextObject]) ) {
        [sketchWebView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"var e = document.createElement(\"script\"); e.src = \"%@\"; e.type=\"text/javascript\"; document.getElementsByTagName(\"head\")[0].appendChild(e); ", url]];
    }

    [self enableSensors];
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@"[MainViewController webView shouldStartLoadWithRequest]: beginning load of (%i) %@", navigationType, request);
    return YES;
}

/*******************************************************************************************************************/
- (void) disableSensors {
    NSLog(@"MainViewController: disabling sensors...");
    [self stopUpdateLoop];
    [self disableIOHelper];
    [self disableIOPacks];
}
- (void) enableSensors {
    NSLog(@"MainViewController: enabling sensors...");
    [self enableIOHelper];
    [self enableIOPacks];
    [self startUpdateLoop];
}

/*******************************************************************************************************************/
- (void)disableIOHelper {
    [ioHelper disable];
}
- (void)enableIOHelper {
    
    NSString *carrierToneFilePath = [[NSBundle mainBundle] pathForResource:kCarrierToneFileName ofType:kCarrierToneFileType];
    
    [ioHelper enableWithDelegate:self
        useLocation:kNadaMobileIOHelper_useLocation
        useAccelerometer:kNadaMobileIOHelper_useAccelerometer
        useAudio:kNadaMobileIOHelper_useAudio:(kNadaMobileIOHelper_useAudio && sensorToneState):carrierToneFilePath
        useProximity:(kNadaMobileIOHelper_useProximity && sensorProximityState)
        useOrientation:kNadaMobileIOHelper_useOrientation
        useBattery:kNadaMobileIOHelper_useBattery
        useIPodRemote:kNadaMobileIOHelper_useIPodRemote
        //useCamera:YES
        ];
}
/*******************************************************************************************************************/

// set up to receive notifications and send outputs to IOPacks
- (void)enableIOPacks {
    NSLog(@"MainViewController: enableIOPacks");
    BOOL didChange = NO;
    if (userPrefs.enableRemoteIOPack &&!remoteIOPackEnabled) {
        NSLog(@"MainViewController: enableIOPacks: Going to connect to remote IO pack on [%@]", userPrefs.remoteIOPackHostName);
        [remoteIOPackTransport connectToHostName:userPrefs.remoteIOPackHostName port:kDefaultTCPSocketPort];
        [remoteIOPackTransport.ioPack setFlowControl:YES];
        remoteIOPackEnabled = YES;
        didChange = YES;
    }
    if (userPrefs.enableLocalIOPack && !localIOPackEnabled) {
        [localIOPackTransport connectToDev:userPrefs.localIOPackDevPath];
        localIOPackEnabled = YES;
        didChange = YES;
    }
} //enableIOPacks

- (void) startUpdateLoop {
    updateLoopEnabled = YES;
	//[self startUpdateLoopOnThread];
}
- (void) stopUpdateLoop {
    updateLoopEnabled = NO;
	//[self stopUpdateLoopOnTimer];
	//[self stopUpdateLoopOnThread];
}
- (void) initializeUpdateLoop {
    [self initializeUpdateLoopOnTimer];
}

- (void)disableIOPacks {
    NSLog(@"MainViewController: disableIOPacks");
    if (remoteIOPackEnabled || localIOPackEnabled) {
        NSLog(@"MainViewController: disabling IOPacks connection");
        remoteIOPackEnabled = NO;
        localIOPackEnabled = NO;
    } //if enabled
} //disableIOPacks

/************************************************/
- (void)initializeUpdateLoopOnTimer {
    NSLog(@"MainViewController: initializeUpdateLoopOnTimer");

    if (updateLoopCreated) return;

    updateLoopEnabled = NO;
    
    updateTimer = [[NSTimer timerWithTimeInterval: kIOPackPollPeriod
        target:self
        selector:@selector(updateLoopRun)
        userInfo:nil
        repeats: YES]
        retain];
        
    [[NSRunLoop mainRunLoop] addTimer: updateTimer forMode: NSDefaultRunLoopMode];
    
    updateLoopCreated = YES;
    
		
} //initializeUpdateLoopOnTimer

/*
- (void) stopUpdateLoopOnTimer {
    [updateTimer invalidate];
    [updateTimer release];
    updateTimer = nil;
} //stopUpdateLoopOnTimer
*/

- (void) updateLoopRun
{
    if (sketchLoadPhase2Countdown > 0) {
        sketchLoadPhase2Countdown--;
        NSLog(@"sketchLoadPhase2Countdown = %i", sketchLoadPhase2Countdown);
        if (sketchLoadPhase2Countdown == 0) {
            //trigger load phase 2
            [sketchWebView reload];
        }
    }

    if (!updateLoopEnabled) return;
    
    if (localIOPackTransport.ioPack) [self updateIOPackInputs:localIOPackTransport.ioPack];
    if (remoteIOPackTransport.ioPack) [self updateIOPackInputs:remoteIOPackTransport.ioPack];
    
    //check if the sketch is trying to send something
    if (updateLoopCount == kLoopsPerPollForReverseCall) {
        [self pollSketchForReverseCalls];
        updateLoopCount = 0;
    } else {
        updateLoopCount++;
    }
    
    [ioHelper updateAmplitudeMonitor];
	
} //updateLoopRun

- (void) updateIOPackInputs:(IOPack *)ioPack
{
    //printf("*");
    if (![ioPack isReady]) return;

    IOPACK_INPUT_VALUE *values;
    NSString* javaScriptCall;
    int i;
    int ln;
    
    BOOL changedInputs = [ioPack setInputValuesFromHardwareInputs];
    if (changedInputs && ([[ioPack inputValues] length] > 0)) {
        values = [[ioPack inputValues] mutableBytes];
        ln = [[ioPack inputValues] length];
        //the inputs from one an IO pack changed, so tell the sketch
        @try {
            javaScriptCall = [NSString stringWithFormat:@"%@(\"%@\", [",
                kSketchCallbackOnIOPack,
                [ioPack deviceAddress]];
            for (i = 0; i < ln; i++) {
                if (i > 0) javaScriptCall = [javaScriptCall stringByAppendingString:@","];
                javaScriptCall = [javaScriptCall stringByAppendingString:
                                 [NSString stringWithFormat:@"%d", values[i]]];
            }
            javaScriptCall = [javaScriptCall stringByAppendingString:@"])"];
            
            //NSLog(@"MainViewController updateLoop run: going to call JS: %@", javaScriptCall);
            
            [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
            
            
            /*
            if (self.dangerousFlipside)
                [self.dangerousFlipside
                    updateIOPackMonitor:[ioPack deviceAddress]
                    inputs:javaScriptCall
                    outputs:@""];
            */
            
        }
        @catch (id theException) {
            NSLog(@"Exception while trying to make %@ callback to sketch: %@", kSketchCallbackOnIOPack, theException);
        }
    }
}

/*****************************************************************************************/

- (void) pollSketchForReverseCalls {
    //NSLog(@"pollSketchForReverseCalls");
    NSString *javaScriptCall = [NSString stringWithFormat:@"%@()", kSketchReverseCallPollFcn];
    NSString *valuesStr = [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
    if ([valuesStr length] > 0) {
        //NSLog(@"Sketch made reverse call: %@", valuesStr);
        
        NSArray *parts = [valuesStr componentsSeparatedByString:kSketchReverseCallSeparator];
        //NSLog(@"Parts = %@", parts);
        
        if ([parts count] < 1)  return; //do nothing if there are no call parts
        NSString* opcode = (NSString*)[parts objectAtIndex:0];
        
        //NSLog(@"Sketch is calling fcn opcode %@", opcode);
        
        //switch (opcode) {
        
        /**************************************
         * reverse call: setIOPack
         *
         */
        if ([opcode isEqualToString:REVERSE_FCN_setIOPack]) {
            //check the number of arguments
            if ([parts count] < ([remoteIOPackTransport.ioPack numOutputChannels] + 2)) return; //1 + number of expected args
            //NSLog(@"Device address is @\"%@\". Is remote? %i", [parts objectAtIndex:1], ([parts objectAtIndex:1] == kDeviceAddressSchemeRemote));
            @try {
                
                if
                (    [[parts objectAtIndex:1] isEqualToString:kDeviceAddressSchemeRemote]
                        ||
                        [[parts objectAtIndex:1] isEqualToString:[remoteIOPackTransport.ioPack deviceAddress]]
                ) {
                    if (userPrefs.enableRemoteIOPack && ![remoteIOPackTransport.ioPack isReady]) {
                        remoteIOPackEnabled = NO;
                        [self enableIOPacks];
                    }
                    if ([remoteIOPackTransport.ioPack isReady]) [self updateIOPackOutputs:remoteIOPackTransport.ioPack withNewValues:parts];

                } else if
                (    [[parts objectAtIndex:1] isEqualToString:kDeviceAddressSchemeLocal]
                        ||
                        [[parts objectAtIndex:1] isEqualToString:[localIOPackTransport.ioPack deviceAddress]]
                ) {
                    if (userPrefs.enableLocalIOPack && ![localIOPackTransport.ioPack isReady]) {
                        localIOPackEnabled = NO;
                        [self enableIOPacks];
                    }
                    if ([localIOPackTransport.ioPack isReady]) [self updateIOPackOutputs:localIOPackTransport.ioPack withNewValues:parts];
                }
                
            }
            @catch (id theException) {
                NSLog(@"Exception while trying to convert values from sketch to ints: %@", theException);
            }
        
        /**************************************
         * reverse call: setSensorTone
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_setSensorTone]) {
            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            BOOL state = [((NSString*)[parts objectAtIndex:1]) isEqualToString:@"true"];
            if (state != sensorToneState) {
                NSLog(@"pollSketchForReverseCalls: setSensorTone %i", state);
                sensorToneState = state;
                [self disableIOHelper];
                [self enableIOHelper];
            }
        
        /**************************************
         * reverse call: setProximitySensor
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_setProximitySensor]) {
            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            BOOL state = [((NSString*)[parts objectAtIndex:1]) isEqualToString:@"true"];
            if (state != sensorProximityState) {
                sensorProximityState = state;
                [self disableIOHelper];
                [self enableIOHelper];
            }

        /**************************************
         * reverse function: soundPlay
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_soundPlay]) {

            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            
            NSURL *soundURL;
            NSString *raw = ((NSString*)[parts objectAtIndex:1]);
            
            if ([raw isEqualToString:kPlaySoundVibrate]) {
                [ioHelper vibrate];
                
            } else if (![raw isEqualToString:@""]) {
                NSURL *rawURL = [[NSURL URLWithString:raw] standardizedURL];
                
                if ([rawURL scheme] != nil) {
                    //absolute URL
                    soundURL = rawURL;
                    
                } else {
                    //relative URL, so resolve it
                    NSURL *currentURL = [[sketchWebView request] URL];
                    soundURL = [[NSURL
                        URLWithString:[NSString
                            stringWithFormat:@"%@/../%@",
                                currentURL,
                                raw]
                            ] standardizedURL];
                }
                
                if (soundURL) {
                    NSLog(@"playSound: Calling playShortSound for %@", soundURL);
                    [ioHelper playSound:soundURL];
                } else {
                    NSLog(@"playSound: Invalid URL %@", raw);
                }
            } else {
                //stop the sounds
                [ioHelper stopAllSounds];
            }

        /**************************************
         * reverse function: videoPlay
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_videoPlay]) {

            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            
            NSURL *videoURL;
            NSString *raw = ((NSString*)[parts objectAtIndex:1]);
            
            if (![raw isEqualToString:@""]) {
                NSURL *rawURL = [[NSURL URLWithString:raw] standardizedURL];
                
                if ([rawURL scheme] != nil) {
                    //absolute URL
                    videoURL = rawURL;
                    
                } else {
                    //relative URL, so resolve it
                    NSURL *currentURL = [[sketchWebView request] URL];
                    videoURL = [[NSURL
                        URLWithString:[NSString
                            stringWithFormat:@"%@/../%@",
                                currentURL,
                                raw]
                            ] standardizedURL];
                }
                
                if (videoURL) {
                    NSLog(@"playVideo: Calling playVideo for %@", videoURL);
                    [ioHelper playVideo:videoURL];
                } else {
                    NSLog(@"playVideo: Invalid URL %@", raw);
                }
            } else {
                //stop the sounds
                [ioHelper stopAllSounds];
            }
            
            
        /**************************************
         * reverse function: soundRecord
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_soundRecord]) {
        
            //check the number of arguments
            if ([parts count] < 1) return; //1 + number of expected args
            //TODO
            
        /**************************************
         * reverse function: captureCamera
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_cameraRead]) {
        
            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            
            if (self.cameraController) {
                NSLog(@"Calling takePicture %@", self.cameraController);
#if __IPHONE_3_1
                if (self.lastCameraImage == nil) {
                    saveLastCameraImageAsFullRes = [((NSString*)[parts objectAtIndex:1]) isEqualToString:@"true"];
                    [self.cameraController takePicture];
                } else {
                    //already saving the last picture
                    NSLog(@"takePicture requested, but ignoring because last request is still being filled");
                }
#endif
            } else if (!warnedAboutMissingCamera) {
                warnedAboutMissingCamera = YES;
                UIAlertView* alertView = [[UIAlertView alloc] initWithTitle:@"Cannot Access Camera" message:@"This device does not have a camera, or the camera cannot be accessed because it is being used by another application." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
                [alertView show];
                [alertView release];
            }
            
        /**************************************
         * reverse function: showCameraPreviewInBackground
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_showCameraPreviewInBackground]) {
        
            //check the number of arguments
            if ([parts count] < 1) return; //1 + number of expected args
            
            NSLog(@"Setting view background to clear");
            self.view.backgroundColor = [UIColor clearColor];
            
        /**************************************
         * reverse function: hideCameraPreview
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_hideCameraPreview]) {
        
            //check the number of arguments
            if ([parts count] < 1) return; //1 + number of expected args
            
            NSLog(@"Setting view background to opaque");
            self.view.backgroundColor = [UIColor blackColor];

            
        /**************************************
         * reverse function: tweet
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_tweet]) {
        
            NSLog(@"Called tweet");
            
            //check the number of arguments
            if ([parts count] < 5) return; //1 + number of expected args

            NSString *username = ((NSString*)[parts objectAtIndex:1]);
            NSString *password = ((NSString*)[parts objectAtIndex:2]);
            NSString *message = ((NSString*)[parts objectAtIndex:3]);
            NSString *sPhotoUrl = ((NSString*)[parts objectAtIndex:4]);
            
            NSURL *photoUrl = [NSURL URLWithString:sPhotoUrl];
            NSString *photoPath;
            if (photoUrl) {
                photoPath = [photoUrl path];
            } else {
                photoPath = nil;
            }
            
            if ((username != nil) && (password != nil) && (message != nil) && ![username isEqualToString:@""] && ![password isEqualToString:@""]) {
            
                NSLog(@"Calling sendTwitpic... %@, %@, %@, %@", username, password, message, photoPath);
                
                [ioHelper sendTwitpicWithNetworkQueueWithUsername:(NSString*)username /*:(ASINetworkQueue*)queue*/
                    andPassword:password
                    andMessage:message
                    andImage:(photoPath != nil) ? [UIImage imageWithContentsOfFile:photoPath] : nil
                ];
            }

            
        /**************************************
         * reverse function: socketAccept
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_socketAccept]) {
        
            NSLog(@"Called socketAccept");
            
            //check the number of arguments
            if ([parts count] < 1) return; //1 + number of expected args
            
            [ioHelper simpleSocketAccept];
            
        /**************************************
         * reverse function: socketSend
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_socketSend]) {
        
            NSLog(@"Called socketSend");
            
            //check the number of arguments
            if ([parts count] < 3) return; //1 + number of expected args

            NSString *addr = ((NSString*)[parts objectAtIndex:1]);
            NSString *msg = ((NSString*)[parts objectAtIndex:2]);

            [ioHelper simpleSocketSendTo:addr message:msg async:NO];

            
        /**************************************
         * reverse function: peerListen
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_peerListen]) {
        
            NSLog(@"Called peerListen");
            
            //check the number of arguments
            if ([parts count] < 2) return; //1 + number of expected args
            
            NSString *myID = ((NSString*)[parts objectAtIndex:1]);

            [ioHelper messengerAccept:myID];
            
        /**************************************
         * reverse function: peerSend
         *
         */
        } else if ([opcode isEqualToString:REVERSE_FCN_peerSend]) {
        
            NSLog(@"Called peerSend");
            
            //check the number of arguments
            if ([parts count] < 4) return; //1 + number of expected args

            NSString *toID = ((NSString*)[parts objectAtIndex:1]);
            NSString *fromID = ((NSString*)[parts objectAtIndex:2]);
            NSString *msg = ((NSString*)[parts objectAtIndex:3]);

            [ioHelper messengerSendTo:toID from:fromID message:msg];

        /**************************************/
        }
    }
} //pollSketchForReverseCalls

- (void) updateIOPackOutputs:(IOPack *)ioPack withNewValues:(NSArray *)parts
{
    //NSLog(@"[MainViewController updateIOPackOutputs]:--");
    IOPACK_OUTPUT_VALUE *values = [[ioPack outputValues] mutableBytes];
    int ln = [[ioPack outputValues] length];
    
    for (int i = 0; i < ln; i++) {
        values[i] = (IOPACK_OUTPUT_VALUE)[[parts objectAtIndex:(i+2)] intValue];
        //NSLog(@"[MainViewController updateIOPackOutputs]: %d = %d", i, values[i]);
    }
    [ioPack setHardwareOutputsFromOutputValues];
    
    //NSLog(@"[MainViewController updateIOPackOutputs]:--");
}

/*******************************************************************************************************************
 *
 * NADAMobileIOHelper delegate methods
 */

- (void)nada:(NADAMobileIOHelper *)nada didAccelerate:(UIAcceleration*)acceleration {
    //NSLog(@"MainViewController: nada didAccelerate");
	//DO NOT USE retainedJavaScriptCall HERE!!!
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f, %f, %f)",
        kSketchCallbackOnAccelerate,
        acceleration.x,
        acceleration.y,
        acceleration.z
        ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    //NSLog(@"MainViewController: nada didUpdateToLocation");
    //DO NOT USE retainedJavaScriptCall HERE!!!
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f, %f, %f)",
        kSketchCallbackOnLocation,
        newLocation.coordinate.latitude,
        newLocation.coordinate.longitude,
        newLocation.altitude
        ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

#if __IPHONE_3_0
- (void)nada:(NADAMobileIOHelper *)nada didUpdateHeading:(CLHeading *)newHeading {
    //NSLog(@"MainViewController: nada didUpdateHeading");
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f)",
                                kSketchCallbackOnHeading,
                                [newHeading magneticHeading]
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}
#endif

- (void)nada:(NADAMobileIOHelper *)nada
        didUpdateAudioAmplitude:(AMIOAmplitudeValue)amp
        average:(AMIOAmplitudeValue)avg 
        low:(AMIOAmplitudeValue)l
        mid:(AMIOAmplitudeValue)m
        high:(AMIOAmplitudeValue)h
{
    //NSLog(@"MainViewController: nada didUpdateAudioAmplitude (%f)", amp);
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f)",
                                kSketchCallbackOnAudioAmplitude,
                                amp
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];

    NSString* javaScriptCall2 = [NSString stringWithFormat:@"%@(%f, %f, %f)",
                                kSketchCallbackOnAudioProfile,
                                l, m, h
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall2];
}

- (void)nada:(NADAMobileIOHelper *)nada didChangeProximity:(BOOL)isNearObject
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%@)",
                                kSketchCallbackOnProximity,
                                isNearObject ? kJsTRUE : kJsFALSE
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didChangeOrientation:(UIDeviceOrientation)orientation
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(\"%@\")",
                                kSketchCallbackOnOrientation,
                                orientation == UIDeviceOrientationPortrait ? @"portrait" :
                                orientation == UIDeviceOrientationPortraitUpsideDown ? @"portraitUpsideDown" :
                                orientation == UIDeviceOrientationLandscapeLeft ? @"landscapeLeft" :
                                orientation == UIDeviceOrientationLandscapeRight ? @"landscapeRight" :
                                orientation == UIDeviceOrientationFaceUp ? @"faceUp" :
                                orientation == UIDeviceOrientationFaceDown ? @"faceDown" :
                                @"unknown"
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didChangeBattery:(float)batteryLevel
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f)",
                                kSketchCallbackOnBattery,
                                batteryLevel
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didReceiveSimpleSocketMessage:(NSString *)msg from:(NSString *)addr
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(\"@%\",\"%@\")",
                                kSketchCallbackOnSimpleSocketRecv,
                                addr,
                                msg
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didReceiveFromMessenger:(NSString *)msg from:(NSString *)fromID
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(\"@%\",\"%@\")",
                                kSketchCallbackOnSimpleSocketRecv,
                                fromID,
                                msg
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada iPodDidChangeStatePlaying:(BOOL)playing paused:(BOOL)paused
{
    //NSLog(@"iPodDidChangeStatePlaying %i %i", playing, paused);
    NSString* javaScriptCall;
    if (playing) {
        javaScriptCall = [NSString stringWithFormat:@"%@()",
                                    kSketchCallbackOnIPodPlay
                                    ];
    } else if (pause) {
        javaScriptCall = [NSString stringWithFormat:@"%@()",
                                    kSketchCallbackOnIPodPause
                                    ];
    }

    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}

- (void)nada:(NADAMobileIOHelper *)nada didChangeIPodVolume:(float)volume
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(%f)",
                                kSketchCallbackOnIPodVolume,
                                volume
                                ];
    //NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}



/******************************************************************************************************
 * Camera methods
 *
 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    NSLog(@"MainViewController imagePickerControllerDidCancel");
    //TOD
}


- (void)imagePickerController:(UIImagePickerController *)picker
    didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSLog(@"MainViewController didFinishPickingMediaWithInfo");

    if (info == nil) {
        //cancelled
        //TODO - do something?
        NSLog(@"Media info is nil...");
        
    } else if (self.lastCameraImage != nil) {
        //camera data is already in use!!
        //TODO - do something?
        NSLog(@"Camera is busy...");
        
    } else {
        //get the image data
        if (saveLastCameraImageAsFullRes) {
            NSLog(@"Creating full-size UIImage");
            self.lastCameraImage = (UIImage *)[info objectForKey:UIImagePickerControllerOriginalImage];
        } else {
            NSLog(@"Creating low-res UIImage");
            //TODO - create low-res preview instead of full-sized image
            //self.lastCameraImage = (UIImage *)[info objectForKey:UIImagePickerControllerOriginalImage];
            self.lastCameraImage = (UIImage *)[info objectForKey:UIImagePickerControllerOriginalImage];
            UIGraphicsBeginImageContext(CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height));
            [self.lastCameraImage
                drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)
                blendMode:kCGBlendModeNormal
                alpha:1.0
                ];
            self.lastCameraImage = UIGraphicsGetImageFromCurrentImageContext();
            //[img autorelease];
            UIGraphicsEndImageContext();
        }
        if (self.lastCameraImage) {
            //spawn a new thread to save it to disk
            NSLog(@"saveImageToDocuments: going to detach thread to store image");
            [NSThread detachNewThreadSelector:@selector(saveLastCameraImage:) toTarget:self withObject:lastCameraImage];
            self.lastCameraImage = nil;
        }
        
    }
}

- (void)saveLastCameraImage:(UIImage *)image {
    // You must create a autorelease pool for all secondary threads.
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    //where to save
    NSString *path = [SketchUpdateController reserveRuntimeImagePath:@""];
    
    //store the data for the image
    NSLog(@"Getting image data");
    if (YES == [UIImagePNGRepresentation(image) writeToFile:path atomically:NO]) {
        self.lastCameraImageURL = [NSURL fileURLWithPath:path];
        
        NSLog(@"didFinishCameraCapture - Saved to %@ url=%@", path, self.lastCameraImageURL);
        
        [self performSelectorOnMainThread:@selector(notifySketchOfSavedCameraImage:) withObject:lastCameraImageURL waitUntilDone:YES];
        self.lastCameraImage = nil;
        
    } else {
        NSLog(@"saveImageToDocuments writing %@ failed", path);
    }

    //and release the pool
    [pool release];
}


-(void)notifySketchOfSavedCameraImage:(NSURL *)url
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(\"%@\")",
                                kSketchCallbackOnCameraReadComplete,
                                url
                                ];
    NSLog(javaScriptCall);
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}


- (void)notifySketchOfSavedSound:(NSURL *)url
{
    NSString* javaScriptCall = [NSString stringWithFormat:@"%@(\"%@\")",
                                kSketchCallbackOnSoundRecordComplete,
                                url
                                ];
    [sketchWebView stringByEvaluatingJavaScriptFromString:javaScriptCall];
}


@end

